🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / settings / src / commonMain / kotlin / org / meshtastic / feature / settings / radio / component / MeshBeaconConfigPolicy.kt
Displaying Raw • Download
feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/MeshBeaconConfigPolicy.kt renovate/fastlane-2.x-lockfile (056eed64) Text, 11.19 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.settings.radio.component
Tff7b72import T7ee787org.meshtastic.core.model.RegionPresetConstraint
Tff7b72import T7ee787org.meshtastic.core.model.constraintFor
Tff7b72import T7ee787org.meshtastic.core.model.util.isChannelPlaceholder
Tff7b72import T7ee787org.meshtastic.feature.settings.util.FixedUpdateIntervals
Tff7b72import T7ee787org.meshtastic.proto.ChannelSettings
Tff7b72import T7ee787org.meshtastic.proto.Config
Tff7b72import T7ee787org.meshtastic.proto.Config.LoRaConfig.ModemPreset
Tff7b72import T7ee787org.meshtastic.proto.LoRaRegionPresetMap
Tff7b72import T7ee787org.meshtastic.proto.ModuleConfig.MeshBeaconConfig
T8b949e/**
* Presets safe to offer a beacon when the firmware gave no region legality map (design#140 behavior 2): no
* `minFirmware` gate, not deprecated, and a bandwidth narrow enough (<=250 kHz) to fit any region's allocation,
* including the narrowest EU bands. Never the raw, unconstrained preset list.
*/
Tff7b72internal Tff7b72val Te6edf3CONSERVATIVE_BEACON_PRESETS Tff7b72=
Te6edf3listOfTb4b4b4(
Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4,
Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_MODERATETb4b4b4,
Te6edf3ModemPresetTb4b4b4.Te6edf3MEDIUM_SLOWTb4b4b4,
Te6edf3ModemPresetTb4b4b4.Te6edf3MEDIUM_FASTTb4b4b4,
Te6edf3ModemPresetTb4b4b4.Te6edf3SHORT_SLOWTb4b4b4,
Te6edf3ModemPresetTb4b4b4.Te6edf3SHORT_FASTTb4b4b4,
Tb4b4b4)
T8b949e/**
* Resolves the preset constraint a beacon's target rows should be filtered by: the firmware's advertised region->preset
* map when it has one, otherwise [CONSERVATIVE_BEACON_PRESETS]. Unlike [constraintFor] alone, this never returns null,
* so a beacon row is never left showing the full unconstrained preset list.
*/
Tff7b72internal Tff7b72fun Td2a8ffbeaconPresetConstraintTb4b4b4(
Te6edf3regionPresetMapTb4b4b4: Te6edf3LoRaRegionPresetMap?Tb4b4b4,
Te6edf3regionTb4b4b4: Te6edf3ConfigTb4b4b4.Te6edf3LoRaConfigTb4b4b4.Te6edf3RegionCodeTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3RegionPresetConstraint Tff7b72= Te6edf3regionPresetMapTb4b4b4.Te6edf3constraintForTb4b4b4(Te6edf3regionTb4b4b4)
Tff7b72?: Te6edf3RegionPresetConstraintTb4b4b4(
Te6edf3presets Tff7b72= Te6edf3CONSERVATIVE_BEACON_PRESETSTb4b4b4,
Te6edf3defaultPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4,
Te6edf3licensedOnly Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)
T8b949e/**
* Returns [storedSeconds] when it matches none of [allowed], so the caller can show it as a disabled fallback item
* (design#140's never-render-blank rule); null when the stored value is already one of the allowed intervals.
*/
Tff7b72internal Tff7b72fun Td2a8ffbeaconIntervalFallbackTb4b4b4(Te6edf3storedSecondsTb4b4b4: Tffa657LongTb4b4b4, Te6edf3allowedTb4b4b4: Te6edf3ListTff7b72<Te6edf3FixedUpdateIntervalsTff7b72>Tb4b4b4)Tb4b4b4: Tffa657Long? Tff7b72=
Te6edf3storedSecondsTb4b4b4.Te6edf3takeUnless Tb4b4b4{ Te6edf3seconds Tff7b72-Tff7b72> Te6edf3allowedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3seconds Tb4b4b4} Tb4b4b4}
T8b949e/**
* Matches [offerChannel] to its true `channel_index` among [selectableChannels] by name and PSK (a channel's identity
* for beacon purposes); null when [offerChannel] is unset, names a channel the radio no longer has, or only matches a
* placeholder slot excluded from [selectableChannels] (design#140 Q2) -- either way the caller falls back to showing it
* as a stale disabled item rather than a spuriously-selected placeholder.
*/
Tff7b72internal Tff7b72fun Td2a8ffbeaconOfferChannelIndexTb4b4b4(
Te6edf3offerChannelTb4b4b4: Te6edf3ChannelSettings?Tb4b4b4,
Te6edf3selectableChannelsTb4b4b4: Te6edf3ListTff7b72<Te6edf3PairTff7b72<Tffa657IntTb4b4b4, Te6edf3ChannelSettingsTff7b72>Tff7b72>Tb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657Int? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3offerChannel Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3selectableChannels
Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tb4b4b4(Te6edf3_Tb4b4b4, Te6edf3settingsTb4b4b4) Tff7b72-Tff7b72> Te6edf3settingsTb4b4b4.Te6edf3name Tff7b72=Tff7b72= Te6edf3offerChannelTb4b4b4.Te6edf3name Tff7b72&Tff7b72& Te6edf3settingsTb4b4b4.Te6edf3psk Tff7b72=Tff7b72= Te6edf3offerChannelTb4b4b4.Te6edf3psk Tb4b4b4}
Tff7b72?.Te6edf3first
Tb4b4b4}
T8b949e/**
* Applies design#140's save-time invariants to [form] before it is written. When [radioLora] uses a standard modem
* preset (`use_preset = true`): the radio's own region (behavior 1) and configured preset (behavior 4) are always
* stamped, never user-chosen; every broadcast target's region is kept in lockstep; and an untouched offered channel
* defaults to the radio's primary channel (behavior 3's required-channel rule), while an already-set offered channel
* (even one that no longer matches a radio channel) is kept. Only the offered channel's name and PSK are carried over,
* never the radio's own channel_index/id/uplink/downlink/module flags, which have no meaning for a channel someone
* else's radio is being invited to join.
*
* When the radio uses custom LoRa parameters (`use_preset = false`), `modem_preset` is meaningless, so every broadcast
* field instead carries over from [stored] verbatim: stamping a stale preset would mint a live on-air lie about what
* the mesh actually runs, and firmware transmits these fields with no validation against the radio's own live
* parameters. Only the flags (listen/broadcast) come from [form]; the editor itself only allows turning broadcast off
* in this state, never on.
*/
Tff7b72internal Tff7b72fun Td2a8ffstampBeaconConfigForSaveTb4b4b4(
Te6edf3formTb4b4b4: Te6edf3MeshBeaconConfigTb4b4b4,
Te6edf3storedTb4b4b4: Te6edf3MeshBeaconConfigTb4b4b4,
Te6edf3radioLoraTb4b4b4: Te6edf3ConfigTb4b4b4.Te6edf3LoRaConfigTb4b4b4,
Te6edf3channelListTb4b4b4: Te6edf3ListTff7b72<Te6edf3ChannelSettingsTff7b72>Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3MeshBeaconConfig Tff7b72= Tff7b72if Tb4b4b4(Te6edf3radioLoraTb4b4b4.Te6edf3use_presetTb4b4b4) Tb4b4b4{
Te6edf3formTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3broadcast_offer_region Tff7b72= Te6edf3radioLoraTb4b4b4.Te6edf3regionTb4b4b4,
Te6edf3broadcast_offer_preset Tff7b72= Te6edf3radioLoraTb4b4b4.Te6edf3modem_presetTb4b4b4,
Te6edf3broadcast_offer_channel Tff7b72=
Tb4b4b4(Te6edf3formTb4b4b4.Te6edf3broadcast_offer_channel Tff7b72?: Te6edf3channelListTb4b4b4.Te6edf3getOrNullTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3ChannelSettingsTb4b4b4(Te6edf3name Tff7b72= Tffa657itTb4b4b4.Te6edf3nameTb4b4b4, Te6edf3psk Tff7b72= Tffa657itTb4b4b4.Te6edf3pskTb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3broadcast_targets Tff7b72= Te6edf3formTb4b4b4.Te6edf3broadcast_targetsTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3region Tff7b72= Te6edf3radioLoraTb4b4b4.Te6edf3regionTb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3formTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3broadcast_message Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_messageTb4b4b4,
Te6edf3broadcast_interval_secs Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_interval_secsTb4b4b4,
Te6edf3broadcast_offer_region Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_offer_regionTb4b4b4,
Te6edf3broadcast_offer_preset Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_offer_presetTb4b4b4,
Te6edf3broadcast_offer_channel Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_offer_channelTb4b4b4,
Te6edf3broadcast_targets Tff7b72= Te6edf3storedTb4b4b4.Te6edf3broadcast_targetsTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Applies a channel pick to one broadcast target row (design#140 behavior 7): the radio's currently-configured preset
* is preselected only the first time the row gets a concrete channel, never overwriting a preset the user already
* chose. Picking the "Default" sentinel (`channelIndex = null`, design#140 behavior 6) leaves any existing preset
* untouched rather than preselecting -- there is no channel to have "just been picked".
*/
Tff7b72internal Tff7b72fun Td2a8ffselectBeaconTargetChannelTb4b4b4(
Te6edf3targetTb4b4b4: Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTb4b4b4,
Te6edf3channelIndexTb4b4b4: Tffa657Int?Tb4b4b4,
Te6edf3currentPresetTb4b4b4: Te6edf3ModemPresetTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTarget Tff7b72= Te6edf3targetTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3channel_index Tff7b72= Te6edf3channelIndexTb4b4b4,
Te6edf3preset Tff7b72= Tff7b72if Tb4b4b4(Te6edf3channelIndex Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Te6edf3targetTb4b4b4.Te6edf3preset Tff7b72?: Te6edf3currentPreset Tff7b72else Te6edf3targetTb4b4b4.Te6edf3presetTb4b4b4,
Tb4b4b4)
T8b949e/**
* Seeds an empty stored `broadcast_targets` list with one default row (design#140 behavior 6: "one row saves as the
* single broadcast config, added rows save as broadcast targets"). Firmware's own fallback for an empty list is a
* single beacon on the node's running preset and region over the primary channel (`MeshBeaconModule.cpp::sendBeacon`);
* seeding a row here makes that default visible and editable in the editor instead of leaving it implicit. A non-empty
* stored list is returned unchanged.
*/
Tff7b72internal Tff7b72fun Td2a8ffseedBeaconTargetsTb4b4b4(Te6edf3storedTb4b4b4: Te6edf3ListTff7b72<Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTff7b72> Tff7b72=
Te6edf3storedTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Te6edf3listOfTb4b4b4(Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e/**
* [MeshBeaconConfigScreen]'s actual `formState` initial value: [seedBeaconTargets] applied to the loaded config's
* `broadcast_targets`, everything else untouched. Pulled out as its own function (rather than inlined at the
* `rememberConfigState` call site) so a test can call the exact production entry point instead of separately calling
* [seedBeaconTargets] and only asserting against it in isolation.
*/
Tff7b72internal Tff7b72fun Td2a8ffinitialBeaconFormStateTb4b4b4(Te6edf3loadedTb4b4b4: Te6edf3MeshBeaconConfigTb4b4b4)Tb4b4b4: Te6edf3MeshBeaconConfig Tff7b72=
Te6edf3loadedTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3broadcast_targets Tff7b72= Te6edf3seedBeaconTargetsTb4b4b4(Te6edf3loadedTb4b4b4.Te6edf3broadcast_targetsTb4b4b4)Tb4b4b4)
T8b949e/**
* Removes the target row at [index], keeping the list at a floor of one row (design#140 behavior 6: "no UI that changes
* shape once targets exist" -- the list must never render with zero rows, since zero rows would hide firmware's
* implicit single-target fallback rather than represent "no beacon"). Removing the only remaining row replaces it with
* a fresh default row instead of emptying the list, mirroring the reference iOS editor.
*/
Tff7b72internal Tff7b72fun Td2a8ffremoveBeaconTargetTb4b4b4(
Te6edf3targetsTb4b4b4: Te6edf3ListTff7b72<Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTff7b72>Tb4b4b4,
Te6edf3indexTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTff7b72> Tff7b72=
Te6edf3targetsTb4b4b4.Te6edf3filterIndexed Tb4b4b4{ Te6edf3iTb4b4b4, Te6edf3_ Tff7b72-Tff7b72> Te6edf3i Tff7b72!Tff7b72= Te6edf3index Tb4b4b4}Tb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Te6edf3listOfTb4b4b4(Te6edf3MeshBeaconConfigTb4b4b4.Te6edf3BroadcastTargetTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e/** The three broadcast-half gating decisions design#140 Q1 hangs off `radioLora.use_preset` and the STORED flag. */
Tff7b72internal Tff7b72data Tff7b72class T56d364BeaconBroadcastGateTb4b4b4(
Tff7b72val Te6edf3sectionsVisibleTb4b4b4: Tffa657BooleanTb4b4b4,
Tff7b72val Te6edf3sectionsEnabledTb4b4b4: Tffa657BooleanTb4b4b4,
Tff7b72val Te6edf3toggleEnabledTb4b4b4: Tffa657BooleanTb4b4b4,
Tb4b4b4)
T8b949e/**
* Resolves [BeaconBroadcastGate] for the BROADCAST half of the editor (design#140 Q1). Keyed off
* [storedBroadcastEnabled] (the flag as last saved), not any live form value, so an in-session toggle-off is always
* recoverable: sections stay visible (but non-editable) whenever the radio was already broadcasting, and only fully
* editable when the radio uses a standard modem preset.
*/
Tff7b72internal Tff7b72fun Td2a8ffbeaconBroadcastGateTb4b4b4(
Te6edf3connectedTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3useStandardPresetTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3storedBroadcastEnabledTb4b4b4: Tffa657BooleanTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3BeaconBroadcastGate Tb4b4b4{
Tff7b72val Te6edf3visible Tff7b72= Te6edf3useStandardPreset Tff7b72|Tff7b72| Te6edf3storedBroadcastEnabled
Tff7b72return Te6edf3BeaconBroadcastGateTb4b4b4(
Te6edf3sectionsVisible Tff7b72= Te6edf3visibleTb4b4b4,
Te6edf3sectionsEnabled Tff7b72= Te6edf3connected Tff7b72&Tff7b72& Te6edf3useStandardPresetTb4b4b4,
Te6edf3toggleEnabled Tff7b72= Te6edf3connected Tff7b72&Tff7b72& Te6edf3visibleTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* A beacon needs a channel to offer (behavior 3) and, when broadcasting, an interval of at least one hour; both are
* moot when the broadcast half is gated off (design#140 Q1), since save then preserves the stored broadcast fields
* verbatim regardless of the live channel list or interval.
*/
Tff7b72internal Tff7b72fun Td2a8ffmeshBeaconSaveEnabledTb4b4b4(
Te6edf3connectedTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3useStandardPresetTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3intervalValidTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hasChannelsTb4b4b4: Tffa657BooleanTb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3connected Tff7b72&Tff7b72& Tb4b4b4(Tff7b72!Te6edf3useStandardPreset Tff7b72|Tff7b72| Tb4b4b4(Te6edf3intervalValid Tff7b72&Tff7b72& Te6edf3hasChannelsTb4b4b4)Tb4b4b4)
T8b949e/**
* Filters [channelList] down to the slots a beacon picker should offer (design#140 Q2), pairing each with its true
* `channel_index` so selection keeps firmware slot semantics. Index 0 (primary) is always kept, even blank: a
* blank-name, zero-psk primary is a legal cleartext channel, not padding. Index >= 1 is kept only when it is not
* [isChannelPlaceholder], matching firmware's own slot-liveness test so the picker shows exactly what firmware will
* accept. Placeholders otherwise render as duplicate fake preset names (several identical "LongFast" rows) via
* `Channel.name`'s empty-name fallback, not as blank rows.
*/
Tff7b72internal Tff7b72fun Td2a8ffselectableBeaconChannelsTb4b4b4(Te6edf3channelListTb4b4b4: Te6edf3ListTff7b72<Te6edf3ChannelSettingsTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PairTff7b72<Tffa657IntTb4b4b4, Te6edf3ChannelSettingsTff7b72>Tff7b72> Tff7b72=
Te6edf3channelList
Tb4b4b4.Te6edf3withIndexTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tb4b4b4(Te6edf3indexTb4b4b4, Te6edf3settingsTb4b4b4) Tff7b72-Tff7b72> Te6edf3index Tff7b72=Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Tff7b72!Te6edf3settingsTb4b4b4.Te6edf3isChannelPlaceholderTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3index Te6edf3to Tffa657itTb4b4b4.Te6edf3value Tb4b4b4}
Served by rngit 1.5.4 - Generated in 0.1s